#include "ObjMesh.h"

#include "Ray.h"
#include "Intersection.h"
#include "utilities.h"

#include <fstream>
#include <iostream>
#include <limits>
#include <GL/gl.h>

#include <QFileInfo>

using namespace std;


ObjMesh::ObjMesh(const string &fileName, FurnitureType ftype)
{
    QFileInfo info(QString::fromStdString(fileName));
    m_name = info.baseName().toStdString();
    m_type = ftype;

    hasTextureCoord = false;

    ifstream file(fileName.data(), ios::in);
    if(!file)
    {
        cerr << "File \"" << fileName << "\" not found." << endl;
        return;
    }

    string type;
    while(file >> type)
    {
        if(type == "v")
        {
            float x, y, z;
            file >> x >> y >> z;

            Point3f v(x, y, z);
            vertices.push_back(v);
            m_bbox.doUnion(v);
        }
        else if(type == "vt")
        {
            float u, v;
            file >> u >> v;

            Point2f p(u, v);
            textureCoord.push_back(p);

            hasTextureCoord = true;
        }
        else if(type == "vn")
        {
            float x, y, z;
            file >> x >> y >> z;

            normals.push_back(Vector3f(x, y, z));
        }
        else if(type == "f")
        {
            int vi[3];
            int ni[3];
            int ti[3];
            string token;

            file >> token;
            int count = sscanf(token.c_str(), "%d/%d/%d", &vi[0], &ti[0], &ni[0]);
            if(count == 1)
            {
                count = sscanf(token.c_str(), "%d//%d", &vi[0], &ni[0]);
                if(count == 1)
                    cerr << "Objeto sem normais" << endl;
            }

            file >> token;
            count = sscanf(token.c_str(), "%d/%d/%d", &vi[1], &ti[1], &ni[1]);
            if(count == 1)
            {
                count = sscanf(token.c_str(), "%d//%d", &vi[1], &ni[1]);
                if(count == 1)
                    cerr << "Objeto sem normais" << endl;
            }

            file >> token;
            count = sscanf(token.c_str(), "%d/%d/%d", &vi[2], &ti[2], &ni[2]);
            if(count == 1)
            {
                count = sscanf(token.c_str(), "%d//%d", &vi[2], &ni[2]);
                if(count == 1)
                    cerr << "Objeto sem normais" << endl;
            }

            Face face;
            face.vertices[0] = &vertices[vi[0]-1];
            face.vertices[1] = &vertices[vi[1]-1];
            face.vertices[2] = &vertices[vi[2]-1];
            face.normals[0] = &normals[ni[0]-1];
            face.normals[1] = &normals[ni[1]-1];
            face.normals[2] = &normals[ni[2]-1];
            face.textureCoord[0] = &textureCoord[ti[0]-1];
            face.textureCoord[1] = &textureCoord[ti[1]-1];
            face.textureCoord[2] = &textureCoord[ti[2]-1];

            faces.push_back(face);
        }
    }

    displayListId = glGenLists(1);
    glNewList(displayListId, GL_COMPILE);
    if(hasTextureCoord)
    {
        glBegin(GL_TRIANGLES);
        for(unsigned int i=0; i<faces.size(); ++i)
        {
            glNormal3fv(&faces[i].normals[0]->x);
            glTexCoord2fv(&faces[i].textureCoord[0]->x);
            glVertex3fv(&faces[i].vertices[0]->x);

            glNormal3fv(&faces[i].normals[1]->x);
            glTexCoord2fv(&faces[i].textureCoord[1]->x);
            glVertex3fv(&faces[i].vertices[1]->x);

            glNormal3fv(&faces[i].normals[2]->x);
            glTexCoord2fv(&faces[i].textureCoord[2]->x);
            glVertex3fv(&faces[i].vertices[2]->x);
        }
        glEnd();
    }
    else
    {
        glBegin(GL_TRIANGLES);
        for(unsigned int i=0; i<faces.size(); ++i)
        {
            glNormal3fv(&faces[i].normals[0]->x);
            glVertex3fv(&faces[i].vertices[0]->x);

            glNormal3fv(&faces[i].normals[1]->x);
            glVertex3fv(&faces[i].vertices[1]->x);

            glNormal3fv(&faces[i].normals[2]->x);
            glVertex3fv(&faces[i].vertices[2]->x);
        }
        glEnd();
    }
    glEndList();
}

bool ObjMesh::intersect(const Ray &ray, Intersection *intersection)
{
    if(!m_bbox.intersect(ray))
        return false;

    const float epsilon = 0.0001;

    bool hit = false;
    float min_dist = numeric_limits<float>::max();
    Face *face;
    float b1, b2;

    for(unsigned int i = 0; i < faces.size(); i++)
    {
        const Point3f& p1 = *faces[i].vertices[0];
        const Point3f& p2 = *faces[i].vertices[1];
        const Point3f& p3 = *faces[i].vertices[2];

        Vector3f e1 = p2 - p1;
        Vector3f e2 = p3 - p1;

        Vector3f s1 = crossProduct(ray.d, e2);
        float det = dotProduct(s1, e1);
        if(isZero(det))
            continue;

        float inv_det = 1.f / det;

        // Cálculo da primeira coordenada baricênctrica
        Vector3f d = ray.o - p1;
        float b1_ = dotProduct(d, s1) * inv_det;
        if(b1_ < 0.f || b1_ > 1.f)
            continue;

        // Cálculo da segunda coordenada baricênctrica
        Vector3f s2 = crossProduct(d, e1);
        float b2_ = dotProduct(ray.d, s2) * inv_det;
        if(b2_ < 0.f || b1_ + b2_ > 1.f)
            continue;

        float t = dotProduct(e2, s2) * inv_det;
        if(t < epsilon)
            continue;

        hit = true;

        if(t < min_dist)
        {
            min_dist = t;
            intersection->t = t;
            face = &faces[i];
            b1 = b1_;
            b2 = b2_;
        }
    }

    if(hit)
    {
        const Vector3f &n1 = *face->normals[0];
        const Vector3f &n2 = *face->normals[1];
        const Vector3f &n3 = *face->normals[2];
        float b0 = 1.f - b1 - b2;

        intersection->n = n1*b0 + n2*b1 + n3*b2;

        if(hasTextureCoord)
        {
            intersection->texCoord[0] = b0*face->textureCoord[0]->x + b1*face->textureCoord[1]->x + b2*face->textureCoord[2]->x;
            intersection->texCoord[1] = b0*face->textureCoord[0]->y + b1*face->textureCoord[1]->y + b2*face->textureCoord[2]->y;
        }

        return true;
    }

    return false;
}

void ObjMesh::draw()
{
    glCallList(displayListId);
}

FurnitureType ObjMesh::getType()
{
    return m_type;
}

string ObjMesh::getName()
{
    return m_name;
}

bool ObjMesh::hasTextureCoords()
{
    return hasTextureCoord;
}
